/*-------------------------------------------------------------------------
 *
 * expandedrecord.h
 *	  复合扩展对象的声明。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/utils/expandedrecord.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef EXPANDEDRECORD_H
#define EXPANDEDRECORD_H

#include "access/htup.h"
#include "access/tupdesc.h"
#include "fmgr.h"
#include "utils/expandeddatum.h"


/*
 * 扩展记录包含在一个私有内存上下文中（因为
 * 所有扩展对象必须如此），并具有如下的控制结构。
 *
 * 如果扩展记录包含一个常规“扁平”元组，如果那是原始
 * 输入并且我们没有对其进行修改。否则，内容由 Datum/isnull 数组
 * 加上类型信息表示。如果我们为了访问目的对原始元组进行了
 * 解构但尚未更改它，我们也可以拥有这两种形式。
 * 对于按引用传递的字段类型，在这种情况下，Datums 将指向
 * 扁平元组。一旦我们开始修改元组字段，新的按引用字段
 * 将在内存上下文中分别 palloc。
 *
 * 可以构建一个扩展记录，该记录引用存储在外部的“扁平”
 * 元组，如果调用者可以保证该元组在扩展记录的生存期内不会
 * 更改。（此 frammish 主要用于避免在触发器函数中不必要的数据复制。）
 */
#define ER_MAGIC 1384727874		/* 用于调试交叉检查的 ID */

typedef struct ExpandedRecordHeader
{
	/* 扩展对象的标准头部 */
	ExpandedObjectHeader hdr;

	/* 识别扩展记录的魔术值（仅用于调试） */
	int			er_magic;

	/* 各种标志位 */
	int			flags;
#define ER_FLAG_FVALUE_VALID	0x0001	/* fvalue 是否是最新的？ */
#define ER_FLAG_FVALUE_ALLOCED	0x0002	/* fvalue 是本地存储？ */
#define ER_FLAG_DVALUES_VALID	0x0004	/* dvalues/dnulls 是否是最新的？ */
#define ER_FLAG_DVALUES_ALLOCED	0x0008	/* 任何字段值是本地存储？ */
#define ER_FLAG_HAVE_EXTERNAL	0x0010	/* 任何字段值是外部的？ */
#define ER_FLAG_TUPDESC_ALLOCED	0x0020	/* tupdesc 是本地存储？ */
#define ER_FLAG_IS_DOMAIN		0x0040	/* er_decltypeid 是域？ */
#define ER_FLAG_IS_DUMMY		0x0080	/* 此头信息是虚的（见下文） */
/* 替换元组数据时不应清除的标志位： */
#define ER_FLAGS_NON_DATA \
	(ER_FLAG_TUPDESC_ALLOCED | ER_FLAG_IS_DOMAIN | ER_FLAG_IS_DUMMY)

	/* 记录变量的声明类型（可能是域类型） */
	Oid			er_decltypeid;

	/*
	 * 实际复合类型/typmod；绝不能是域（如果 ER_FLAG_IS_DOMAIN，
	 * 这些标识复合基础类型）。这些将匹配
	 * er_tupdesc->tdtypeid/tdtypmod，以及由此扩展记录中的
	 * 复合数据生成或存储的头字段。
	 */
	Oid			er_typeid;		/* 复合类型的类型OID */
	int32		er_typmod;		/* 复合类型的typmod */

	/*
	 * 元组描述符，如果我们有一个，否则为NULL。 这可能指向一个
	 * 原本属于typcache的引用计数tupdesc，在这种情况下，我们使用
	 * 内存上下文重置回调来释放引用计数。它也可以在该对象的私有
	 * 上下文中局部分配（在这种情况下设置ER_FLAG_TUPDESC_ALLOCED）。
	 */
	TupleDesc	er_tupdesc;

	/*
	 * 在进程内唯一标识符，用于tupdesc（见typcache.h）。这个
	 * 字段永远不会等于INVALID_TUPLEDESC_IDENTIFIER。
	 */
	uint64		er_tupdesc_id;

	/*
	 * 如果我们有记录的Datum数组表示，它保存在这里；
	 * 否则ER_FLAG_DVALUES_VALID未设置，并且如果dvalues/dnulls 
	 * 尚未分配，则可能为NULL。如果已分配，dvalues和
	 * dnulls数组是palloc'd在对象私有上下文中，并且长度
	 * 与er_tupdesc->natts匹配。对于按引用传递的字段类型，
	 * dvalues条目可能指向fstartptr..fendptr区域，或者
	 * 指向单独palloc'd的块。
	 */
	Datum	   *dvalues;		/* Datum 数组 */
	bool	   *dnulls;			/* is-null 标志的数组用于 Datums */
	int			nfields;		/* 上述数组的长度 */

	/*
	 * flat_size是已知的扩展记录的扁平等效物的当前空间要求；
	 * 否则为0。我们存储这个是为了使连续调用get_flat_size
	 * 便宜。如果flat_size不为0，组件值data_len，hoff和hasnull
	 * 也必须有效。
	 */
	Size		flat_size;

	Size		data_len;		/* flat_size内的数据长度 */
	int			hoff;			/* 头偏移量 */
	bool		hasnull;		/* 需要空位图吗？ */

	/*
	 * 如果我们有扁平表示，fvalue指向它，否则为NULL。
	 * 如果扁平表示有效（最新），则设置ER_FLAG_FVALUE_VALID。
	 * 即使由于用户字段的更改而使扁平表示过时，它仍然可以
	 * 用于获取系统列值。如果我们有扁平表示，那么fstartptr/fendptr
	 * 指向其数据区域的起始和结束+1；这样我们就可以判断哪些Datum
	 * 指针指向扁平表示，而不是指向单独palloc'd数据的指针。
	 */
	HeapTuple	fvalue;			/* 可能是也可能不是私有存储 */
	char	   *fstartptr;		/* 其数据区域的开始 */
	char	   *fendptr;		/* 其数据区域的结束 + 1 */

	/* 扩展记录上的一些操作需要短期上下文 */
	MemoryContext er_short_term_cxt;	/* 短期内存上下文 */

	/* 用于域检查的工作状态，如果设置了ER_FLAG_IS_DOMAIN */
	struct ExpandedRecordHeader *er_dummy_header;	/* 虚拟记录头 */
	void	   *er_domaininfo;	/* domain_check() 的缓存空间 */

	/* 回调信息（如果er_mcb.arg不为NULL，则处于活动状态） */
	MemoryContextCallback er_mcb;
} ExpandedRecordHeader;

/* 扩展记录对象的fmgr宏 */
#define PG_GETARG_EXPANDED_RECORD(n)  DatumGetExpandedRecord(PG_GETARG_DATUM(n))
#define ExpandedRecordGetDatum(erh)   EOHPGetRWDatum(&(erh)->hdr)
#define ExpandedRecordGetRODatum(erh) EOHPGetRODatum(&(erh)->hdr)
#define PG_RETURN_EXPANDED_RECORD(x)  PG_RETURN_DATUM(ExpandedRecordGetDatum(x))

/* 各种其他宏 */
#define ExpandedRecordIsEmpty(erh) \
	(((erh)->flags & (ER_FLAG_DVALUES_VALID | ER_FLAG_FVALUE_VALID)) == 0)
#define ExpandedRecordIsDomain(erh) \
	(((erh)->flags & ER_FLAG_IS_DOMAIN) != 0)

/* 当我们已经有erh时，这可以替代TransferExpandedObject() */
#define TransferExpandedRecord(erh, cxt) \
	MemoryContextSetParent((erh)->hdr.eoh_context, cxt)

/* expanded_record_lookup_field()返回的信息 */
typedef struct ExpandedRecordFieldInfo
{
	int			fnumber;		/* 记录中字段的属性编号 */
	Oid			ftypeid;		/* 字段的类型/typmod信息 */
	int32		ftypmod;
	Oid			fcollation;		/* 字段的排序，如果有的话 */
} ExpandedRecordFieldInfo;

/*
 * 在expandedrecord.c中定义的函数的原型
 */
extern ExpandedRecordHeader *make_expanded_record_from_typeid(Oid type_id, int32 typmod,
															  MemoryContext parentcontext);
extern ExpandedRecordHeader *make_expanded_record_from_tupdesc(TupleDesc tupdesc,
															   MemoryContext parentcontext);
extern ExpandedRecordHeader *make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh,
																 MemoryContext parentcontext);
extern void expanded_record_set_tuple(ExpandedRecordHeader *erh,
									  HeapTuple tuple, bool copy, bool expand_external);
extern Datum make_expanded_record_from_datum(Datum recorddatum,
											 MemoryContext parentcontext);
extern TupleDesc expanded_record_fetch_tupdesc(ExpandedRecordHeader *erh);
extern HeapTuple expanded_record_get_tuple(ExpandedRecordHeader *erh);
extern ExpandedRecordHeader *DatumGetExpandedRecord(Datum d);
extern void deconstruct_expanded_record(ExpandedRecordHeader *erh);
extern bool expanded_record_lookup_field(ExpandedRecordHeader *erh,
										 const char *fieldname,
										 ExpandedRecordFieldInfo *finfo);
extern Datum expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber,
										 bool *isnull);
extern void expanded_record_set_field_internal(ExpandedRecordHeader *erh,
											   int fnumber,
											   Datum newValue, bool isnull,
											   bool expand_external,
											   bool check_constraints);
extern void expanded_record_set_fields(ExpandedRecordHeader *erh,
									   const Datum *newValues, const bool *isnulls,
									   bool expand_external);

/* 外部代码不应直接调用expanded_record_set_field_internal */
#define expanded_record_set_field(erh, fnumber, newValue, isnull, expand_external) \
	expanded_record_set_field_internal(erh, fnumber, newValue, isnull, expand_external, true)

/*
 * 可内联的快速案例。上面的expanded_record_fetch_xxx函数
 * 处理一般案例。
 */

/* 获取扩展记录的实际类型的tupdesc */
static inline TupleDesc
expanded_record_get_tupdesc(ExpandedRecordHeader *erh)
{
	if (likely(erh->er_tupdesc != NULL))
		return erh->er_tupdesc;
	else
		return expanded_record_fetch_tupdesc(erh);
}

/* 获取记录字段的值 */
static inline Datum
expanded_record_get_field(ExpandedRecordHeader *erh, int fnumber,
						  bool *isnull)
{
	if ((erh->flags & ER_FLAG_DVALUES_VALID) &&
		likely(fnumber > 0 && fnumber <= erh->nfields))
	{
		*isnull = erh->dnulls[fnumber - 1];
		return erh->dvalues[fnumber - 1];
	}
	else
		return expanded_record_fetch_field(erh, fnumber, isnull);
}

#endif							/* EXPANDEDRECORD_H */
